﻿using System;
using System.Threading;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using Google.Protobuf;
using UnityEngine;

namespace OutOfUnity
{
    struct NetEvent
    {
        public object pbo;
        public uint msgID;
    }
    public sealed class Network
    {
        public delegate void OnPingTask(object userObject);

        public const int TCPSocket = 1;
        public const int KCPSocket = 2;

        #region StaticInternalVariable
        private static Network instance_;
        private static readonly object lockHolder = new object();
        private static Dictionary<String, NetworkMethod> methods_ = new Dictionary<string, NetworkMethod>();
        #endregion
        #region StaticMethod
        public static Network Instance
        {
            get
            {
                if (instance_ == null)
                {
                    lock (lockHolder)
                    {
                        if (instance_ == null)
                        {
                            instance_ = new Network();
                        }
                    }
                }
                return instance_;
            }
        }
        public static T getProto<T>() where T : new()
        {
            return ObjectPool<T>.Instance.GetObject();
        }
        public static void putProto<T>(T t) where T : new()
        {
            ObjectPool<T>.Instance.PutObject(t);
        }
        public static void update()
        {
            foreach (var kv in methods_)
            {
                kv.Value.loop();
            }
        }
        public static NetworkMethod connect(String name, int type, String ip, int port, OnPingTask pingTask = null, int pingIntval = 3000, object userObject = null)
        {
            var method = new NetworkMethod();
            if (!method.connect(type, ip, port, userObject, pingTask, pingIntval))
            {
                return null;
            }
            methods_[name] = method;
            return method;
        }
        public static int getType(String name)
        {
            switch(name)
            {
                case "tcp":
                    return TCPSocket;
                case "kcp":
                    return KCPSocket;
                default:
                    return 0;
            }
        }
        public static NetworkMethod connect(String address, OnPingTask pingTask = null, int pingIntval = 3000, object userObject = null)
        {
            if (address == null)
            {
                return null;
            }
            // name@tcp://127.0.0.1:12345
            String[] result = address.Split('@');
            if (result.Length != 2)
            {
                return null;
            }
            var name = result[0];
            var url = result[1];
            result = url.Split(new string[] { "://" }, StringSplitOptions.RemoveEmptyEntries);
            if (result.Length != 2)
            {
                return null;
            }
            var type = getType(result[0]);
            var ipAddr = result[1];
            result = ipAddr.Split(':');
            if (result.Length != 2)
            {
                return null;
            }
            var ip = result[0];
            var port = int.Parse(result[1]);
            return connect(name, type, ip, port, pingTask, pingIntval, userObject);
        }
        public static bool stop(String name)
        {
            NetworkMethod method = null;
            if (!methods_.TryGetValue(name, out method))
            {
                return false;
            }
            method.stop();
            methods_.Remove(name);
            return true;
        }
        public static void stop()
        {
            foreach (var kv in methods_)
            {
                kv.Value.stop();
            }
            methods_.Clear();
        }
        public static NetworkMethod get(String name)
        {
            NetworkMethod method = null;
            if (!methods_.TryGetValue(name, out method))
            {
                return null;
            }
            return method;
        }
        #endregion
    }
}
